﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Diagnostics;
using System.Windows.Media;

namespace Master
{
    /**
     * The Toolkit-class contains a collection of methods that act as solutions to common problems.
     * These methods can be accessed from any other class in the application.
     * 
     * @Author A.J. Janssen
     * */
    public static class Toolkit
    {
        private static Random random = new Random();

        /**
         * Method to remove all elements occuring in the 'unwantedElements' list from another list
         * */
        public static void removeAllFromList(List<Car> list, List<Car> unwantedElements)
        {
            foreach (Car unwantedElement in unwantedElements)
                list.Remove(unwantedElement);
        }

        /**
         * Use this method to get the point of a specific element relative to the map
         * Usefull for figuring out coordinates of nested elements
         * */
        public static Point getPointRelativeToMap(UIElement element)
        {
            return element.TransformToAncestor(Map.INSTANCE).Transform(new Point(0, 0));
        }

        /**
         * Get a rect specifiying the current REAL bounds of the given element.
         * This method considers any and all transform applied to the element and return the resulting bounds.
         * */
        public static Rect getBounds(FrameworkElement element)
        {
            GeneralTransform transform = null;
            try { transform = element.TransformToVisual(Map.INSTANCE); }
            catch (Exception e) { if (element is Car) ((Car)element).stopDriving(); return new Rect(); }
            return transform.TransformBounds(new Rect(0, 0, element.ActualWidth, element.ActualHeight));
        }

        /**
         * Method to get a clone of the given bounds, located at the given point
         * */
        public static Rect cloneBoundsAtPoint(Rect bounds, Point p)
        {
           return new Rect(p, new Point(p.X + bounds.Width, p.Y + bounds.Height));
        }

        /**
         * Method to get a clonce of the given bounds, centered at the given point
         * */
        public static Rect cloneBoundsCenteredAtPoint(Rect bounds, Point p)
        {
            Point startPoint = new Point(p.X - (bounds.Width / 2), p.Y - (bounds.Height / 2));
            Point endPoint = new Point(startPoint.X + bounds.Width, startPoint.Y + bounds.Height);
            return new Rect(startPoint, endPoint);
        }

        /**
         * Method to retrieve the centerPoint of a specific element
         * */
        public static Point getCenterPoint(FrameworkElement element)
        {
            Rect bounds = getBounds(element);
            return new Point((bounds.Left + bounds.Right) / 2, (bounds.Top + bounds.Bottom) / 2);
        }

        /**
         * Method to calculate the x-distance between element1 and element2
         * */
        public static double getXDistanceBetweenElements(FrameworkElement element1, FrameworkElement element2)
        {
            Rect element1Bounds = getBounds(element1), element2Bounds = getBounds(element2);
            if (element1Bounds.Right < element2Bounds.Left) return element2Bounds.Left - element1Bounds.Right;
            if (element1Bounds.Left > element2Bounds.Right) return element1Bounds.Left - element2Bounds.Right;
            return 0;
        }

        /**
         * Method to calculate the y-distance between element1 and element2
         * */
        public static double getYDistanceBetweenElements(FrameworkElement element1, FrameworkElement element2)
        {
            Rect element1Bounds = getBounds(element1), element2Bounds = getBounds(element2);
            if (element1Bounds.Bottom < element2Bounds.Top) return element2Bounds.Top - element1Bounds.Bottom;
            if (element1Bounds.Top > element2Bounds.Bottom) return element1Bounds.Top - element2Bounds.Bottom;
            return 0;
        }

        /**
         * Method to calculate the total distance between element1 and element2
         * */
        public static double getTotalDistanceBetweenElements(FrameworkElement element1, FrameworkElement element2)
        {
            return getXDistanceBetweenElements(element1, element2) + getYDistanceBetweenElements(element1, element2);
        }

        /**
         * Method to test if a point falls within the given bounds
         * */
        public static Boolean pointWithinBounds(Rect bounds, Point p)
        {
            return (bounds.Left <= p.X && bounds.Right >= p.X && bounds.Top <= p.Y && bounds.Bottom >= p.Y);
        }

        /**
         * Method to limit the 'limitedBounds' by the bounds specified in 'limitingBounds'
         * */
        public static Rect limitBoundsByBounds(Rect limitedBounds, Rect limitingBounds)
        {
            Point startPoint = limitedBounds.TopLeft, endPoint = limitedBounds.BottomRight;
            if (limitedBounds.Top < limitingBounds.Top) startPoint.Y = limitingBounds.Top;
            if (limitedBounds.Right > limitingBounds.Right) endPoint.X = limitingBounds.Right;
            if (limitedBounds.Bottom > limitingBounds.Bottom) endPoint.Y = limitingBounds.Bottom;
            if (limitedBounds.Left < limitingBounds.Left) startPoint.X = limitingBounds.Left;
            return new Rect(startPoint, endPoint);
        }

        /**
          * Method to test if a certain elements width covers a specific X coordinate
          * */
        public static Boolean elementIsAtX(FrameworkElement element, double x)
        {
            Rect bounds = getBounds(element);
            return (bounds.Left <= x && bounds.Right >= x);
        }

        /**
          * Method to test if a certain elements heigth covers a specific Y coordinate
          * */
        public static Boolean elementIsAtY(FrameworkElement element, double y)
        {
            Rect bounds = getBounds(element);
            return (bounds.Top <= y && bounds.Bottom >= y);
        }

        /**
         * Method to test if a certain element is located within the given bounds
         * */
        public static Boolean elementIsAtBounds(FrameworkElement element, Rect bounds)
        {
            Rect elementBounds = getBounds(element);
            return !(bounds.Right < elementBounds.Left || bounds.Left > elementBounds.Right ||
                    bounds.Bottom < elementBounds.Top || bounds.Top > elementBounds.Bottom);
        }

        /**
         * Use this method to find out if an element occurs at a specific point
         * */
        public static Boolean elementIsAtPoint(FrameworkElement element, Point p)
        {
            Point elementPoint = getPointRelativeToMap(element);
            return (p.X >= elementPoint.X && p.X <= (elementPoint.X + element.ActualWidth) && p.Y >= elementPoint.Y && p.Y <= (elementPoint.Y + element.ActualHeight));
        }

        public static Boolean elementIsAtPoint(Point elementPoint, FrameworkElement element, Point p)
        {
            return (p.X >= elementPoint.X && p.X <= (elementPoint.X + element.ActualWidth) && p.Y >= elementPoint.Y && p.Y <= (elementPoint.Y + element.ActualHeight));
        }

        public static Boolean elementIsAtPoint(Point elementPoint, double elementWidth, double elementHeight, Point p)
        {
            return (p.X >= elementPoint.X && p.X <= (elementPoint.X + elementWidth) && p.Y >= elementPoint.Y && p.Y <= (elementPoint.Y + elementHeight));
        }

        public static Boolean elementIsAtPoint(double elementX, double elementY, double elementWidth, double elementHeight, Point p)
        {
            return (p.X >= elementX && p.X <= (elementX + elementWidth) && p.Y >= elementY && p.Y <= (elementY + elementHeight));
        }

        /**
         * Use this method to grab a random element from a enumeration.
         * 
         * Example: Direction randomDirection = Toolkit.RandomEnum<Direction>();
         * */
        public static T RandomEnum<T>()
        {
            T[] values = (T[])Enum.GetValues(typeof(T));
            return values[random.Next(0, values.Length)];
        }

        /**
         * Use this method to safely add/remove degrees from a orientation (example: 270 + 180 becomes 90)
         * */
        public static int alterOrientationBy(int orientation, int modifier)
        {
            orientation += modifier;
            while (orientation >= 360)
                orientation -= 360;
            while (orientation < 0)
                orientation += 360;
            return orientation;
        }

        /**
         * Method to calculate the smallest difference between 2 Orientations (example: 270 - 1 = 91)
         * */
        public static int getOrientationDifference(int o1, int o2)
        {
            int bigO = (o1 > o2) ? o1 : o2;
            int smallO = (o1 > o2) ? o2 : o1;
            int result = bigO - smallO;
            if (result <= 180) return result;
            return (smallO + 360) - bigO;
        }

        /**
         * Method to convert Side to Orientation
         * */
        public static int sideToOrientation(Side side)
        {
            switch (side)
            {
                case Side.TOP: return 0;
                case Side.RIGHT: return 90;
                case Side.BOTTOM: return 180;
                case Side.LEFT: return 270;
                default: throw new InvalidOperationException("No such side exists");
            }
        }

        /**
         * Method to convert Orientation to Side
         * */
        public static Side orientationToSide(int orientation)
        {
            switch (orientation)
            {
                case 0: return Side.TOP;
                case 90: return Side.RIGHT;
                case 180: return Side.BOTTOM;
                case 270: return Side.LEFT;
                default: throw new InvalidOperationException("Invalid orientation");
            }
        }
    }
}
